import os
import shutil
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
import requests
from PIL import Image, ImageOps
import threading
from datetime import datetime
import time
import random  # Для генерации случайной задержки
import queue
import subprocess # Добавлено для Wi-Fi
import re # Добавлено для Wi-Fi

# Пути к директориям и файлам
avatars_path = r"C:\Users\1\Desktop\VS 2022\Загрузка аватарок ВК\аватарки"
uploaded_path = os.path.join(avatars_path, "Загруженные")
tokens_file_path = r"C:\Users\1\Desktop\VS 2022\Загрузка аватарок ВК\токены для загруки аватарок.txt"
failed_accounts_file = r"C:\Users\1\Desktop\VS 2022\Загрузка аватарок ВК\неудачные.txt"

# Глобальные переменные для токенов и списка аватарок
tokens = []
avatars = []

# Параметры рамки для обработки аватарки
border_size = 110
border_color = 'white'

# ----------------------------------------------------------------------------
# Очереди для логирования и статусы
# ----------------------------------------------------------------------------

log_queue = queue.Queue()
success_queue = queue.Queue()
fail_queue = queue.Queue()
ip_country_queue = queue.Queue() # Новая очередь для IP и страны

# Объект Events для паузы и возобновления
pause_event = threading.Event()
pause_event.set()

# ----------------------------------------------------------------------------
# Создание главного окна с красивым оформлением
# ----------------------------------------------------------------------------

root = tk.Tk()
root.title("Загрузка аватарок ВК")
root.geometry("1200x650") # Немного увеличим высоту для новых меток
root.configure(bg="#2C3E50")

style = ttk.Style(root)
style.theme_use("clam")

default_font = ("Segoe UI", 11)
style.configure("TFrame", background="#2C3E50")
style.configure("TLabel", background="#2C3E50", foreground="#ECF0F1", font=default_font)
style.configure("TButton", font=("Segoe UI", 11, "bold"), padding=6)
style.configure("TEntry", padding=4, font=default_font)

style.map("TButton",
          foreground=[("active", "#ECF0F1")],
          background=[("active", "#2980B9")])

# ----------------------------------------------------------------------------
# Переменные для статистики
# ----------------------------------------------------------------------------

total_accounts = tk.StringVar(value="Всего аккаунтов: 0")
processed_photos = tk.StringVar(value="Обработано фотографий: 0")
remaining_photos = tk.StringVar(value="Осталось фотографий: 0")
successful_uploads = tk.StringVar(value="Успешных добавлений: 0")
failed_uploads = tk.StringVar(value="Неуспешных добавлений: 0")
current_wifi_status = tk.StringVar(value="Wi-Fi: Сканирование...")
current_ip_country_status = tk.StringVar(value="IP: Определение... (Страна: ...)") # Новая переменная

delay_min = tk.StringVar(value="20")
delay_max = tk.StringVar(value="40")

INFO_COLOR = "white"
SUCCESS_COLOR = "#2ECC71"
ERROR_COLOR = "#E74C3C"
SEPARATOR_COLOR = "#3498DB"
DEFAULT_TEXT_COLOR = "#ECF0F1" # Стандартный цвет текста для меток

token_counter = 0
auto_scroll_enabled = tk.BooleanVar(value=True)

# Глобальная ссылка на метку IP/Страны для обновления цвета
ip_country_label_ui_ref = None

# ----------------------------------------------------------------------------
# Функции логирования и работы с очередями (без изменений)
# ----------------------------------------------------------------------------
def log(message, color=INFO_COLOR):
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_queue.put((f"[{timestamp}] {message}\n", color))

def log_success_token(token_info: str):
    success_queue.put(token_info)

def log_fail_token(token_info: str):
    fail_queue.put(token_info)

def process_log_queue():
    try:
        while True:
            msg, color = log_queue.get_nowait()
            log_text.insert(tk.END, msg, color)
            log_text.tag_config(color, foreground=color)
            if auto_scroll_enabled.get():
                log_text.see(tk.END)
    except queue.Empty:
        pass
    root.after(100, process_log_queue)

def process_success_queue():
    try:
        while True:
            token_info = success_queue.get_nowait()
            success_text.insert(tk.END, token_info + "\n")
            if auto_scroll_enabled.get():
                success_text.see(tk.END)
    except queue.Empty:
        pass
    root.after(100, process_success_queue)

def process_fail_queue():
    try:
        while True:
            token_info = fail_queue.get_nowait()
            fail_text.insert(tk.END, token_info + "\n")
            if auto_scroll_enabled.get():
                fail_text.see(tk.END)
    except queue.Empty:
        pass
    root.after(100, process_fail_queue)

# ----------------------------------------------------------------------------
# Функция получения SSID Wi-Fi (для Windows) - без изменений
# ----------------------------------------------------------------------------
def get_current_wifi_ssid():
    try:
        process = subprocess.Popen(['netsh', 'wlan', 'show', 'interfaces'],
                                     stdout=subprocess.PIPE,
                                     text=True,
                                     encoding='utf-8',
                                     errors='ignore',
                                     creationflags=subprocess.CREATE_NO_WINDOW)
        stdout, stderr = process.communicate(timeout=5)
        if process.returncode == 0:
            for line in stdout.splitlines():
                if "SSID" in line and "BSSID" not in line:
                    match = re.search(r":\s*(.+)", line)
                    if match:
                        ssid = match.group(1).strip()
                        if ssid:
                            return ssid
            return "Нет подключения"
        else:
            return "Ошибка опроса"
    except FileNotFoundError:
        return "netsh не найден"
    except subprocess.TimeoutExpired:
        return "Тайм-аут опроса"
    except Exception as e:
        return "Ошибка Wi-Fi"

# ----------------------------------------------------------------------------
# Функция обновления метки Wi-Fi - без изменений
# ----------------------------------------------------------------------------
def update_wifi_display():
    ssid = get_current_wifi_ssid()
    current_wifi_status.set(f"Wi-Fi: {ssid}")
    # Цвет для Wi-Fi метки можно также динамически менять, если нужно
    if "Ошибка" in ssid or "Нет подключения" in ssid or "не найден" in ssid :
        if wifi_label_ui_ref: wifi_label_ui_ref.configure(foreground=ERROR_COLOR)
    else:
        if wifi_label_ui_ref: wifi_label_ui_ref.configure(foreground=DEFAULT_TEXT_COLOR)
    root.after(5000, update_wifi_display)

# ----------------------------------------------------------------------------
# Новые функции для IP и страны
# ----------------------------------------------------------------------------
def get_external_ip_and_country():
    ip_address_val = "Ошибка (IP)"
    country_val = "Ошибка (Страна)"
    try:
        response = requests.get("http://ip-api.com/json/?fields=status,message,country,countryCode,query,city", timeout=4)
        response.raise_for_status()
        data = response.json()
        if data.get("status") == "success":
            ip_address_val = data.get("query", ip_address_val)
            country_name = data.get("country")
            country_code = data.get("countryCode")
            city_name = data.get("city", "") # Попробуем получить город

            if country_name:
                country_val = country_name
            elif country_code:
                country_val = country_code
            else:
                country_val = "N/A"
            
            if city_name and country_val != "N/A": # Добавляем город, если есть
                country_val = f"{city_name}, {country_val}"
            elif city_name: # Если только город, но нет страны
                country_val = city_name

        else:
            error_message = data.get("message", "Неизвестная ошибка от ip-api.com")
            log(f"Ошибка от ip-api.com (status fail): {error_message}", color=ERROR_COLOR)
    except requests.exceptions.Timeout:
        log(f"Таймаут при запросе к ip-api.com", color=ERROR_COLOR)
    except requests.exceptions.RequestException as e:
        log(f"Ошибка запроса к ip-api.com: {e}", color=ERROR_COLOR)
    except Exception as e:
        log(f"Непредвиденная ошибка в get_external_ip_and_country: {e}", color=ERROR_COLOR)
    return ip_address_val, country_val

def _fetch_ip_and_country_data():
    """Выполняется в отдельном потоке для получения IP и страны."""
    ip, country = get_external_ip_and_country()
    ip_country_queue.put((ip, country))

def process_ip_country_queue():
    """Обрабатывает очередь с данными IP/страны и обновляет GUI."""
    global ip_country_label_ui_ref
    try:
        ip, country = ip_country_queue.get_nowait()
        text_to_set = f"IP: {ip} (Локация: {country})"
        current_ip_country_status.set(text_to_set)
        
        if ip_country_label_ui_ref: # Проверяем, создана ли метка
            if "Ошибка" in ip or "Ошибка" in country or "N/A" in country:
                ip_country_label_ui_ref.configure(foreground=ERROR_COLOR)
            else:
                ip_country_label_ui_ref.configure(foreground=DEFAULT_TEXT_COLOR)
    except queue.Empty:
        pass
    root.after(200, process_ip_country_queue) # Проверять очередь чаще

def update_ip_country_display():
    """Запускает получение IP и страны в отдельном потоке и планирует следующий вызов."""
    threading.Thread(target=_fetch_ip_and_country_data, daemon=True).start()
    root.after(15000, update_ip_country_display) # Обновлять каждые 15 секунд

# ----------------------------------------------------------------------------
# Функции работы с файлами и обновление информации (без существенных изменений)
# ----------------------------------------------------------------------------
# ... (остальные функции без изменений, кроме, возможно, логирования)
def log_failed_account(token):
    try:
        with open(failed_accounts_file, 'a', encoding='utf-8') as file:
            file.write(f"{token}\n")
        log(f"Записан неудачный токен: {token}", color=ERROR_COLOR)
    except Exception as e:
        log(f"Ошибка записи неудачного токена: {str(e)}", color=ERROR_COLOR)

def load_tokens():
    global tokens
    if os.path.exists(tokens_file_path):
        with open(tokens_file_path, 'r', encoding='utf-8') as file:
            tokens = [line.strip() for line in file if line.strip()]
        total_accounts.set(f"Всего аккаунтов: {len(tokens)}")
        log(f"Загружено токенов: {len(tokens)}", color=SUCCESS_COLOR)
    else:
        log("Файл с токенами не найден.", color=ERROR_COLOR)

def load_avatars():
    global avatars
    if os.path.exists(avatars_path):
        avatars = [os.path.join(avatars_path, f)
                   for f in os.listdir(avatars_path)
                   if os.path.isfile(os.path.join(avatars_path, f)) and f != "prepared_image.png"]
        remaining_photos.set(f"Осталось фотографий: {len(avatars)}")
        log(f"Загружено аватарок: {len(avatars)}", color=SUCCESS_COLOR)
    else:
        log("Директория с аватарками не найдена.", color=ERROR_COLOR)

def update_info():
    log("Нажата кнопка 'Обновить информацию'", color=INFO_COLOR)
    load_tokens()
    load_avatars()
    processed_photos.set("Обработано фотографий: 0")
    successful_uploads.set("Успешных добавлений: 0")
    failed_uploads.set("Неуспешных добавлений: 0")
    os.makedirs(uploaded_path, exist_ok=True)
    log("Информация обновлена.", color=SUCCESS_COLOR)

def get_user_info(token):
    url = "https://api.vk.com/method/users.get"
    params = {'access_token': token, 'v': '5.131'}
    try:
        response = requests.get(url, params=params, timeout=10)
        if response.status_code == 200:
            data = response.json()
            if 'response' in data and len(data['response']) > 0:
                return data['response'][0]
        log(f"Ошибка получения информации о пользователе: {response.text}", color=ERROR_COLOR)
    except Exception as e:
        log(f"Исключение при получении информации о пользователе: {str(e)}", color=ERROR_COLOR)
    return None

def get_upload_url(token):
    url = "https://api.vk.com/method/photos.getOwnerPhotoUploadServer"
    params = {'access_token': token, 'v': '5.131'}
    try:
        response = requests.get(url, params=params, timeout=10)
        if response.status_code == 200:
            return response.json().get('response', {}).get('upload_url')
        else:
            log(f"Ошибка получения URL: {response.text}", color=ERROR_COLOR)
    except Exception as e:
        log(f"Исключение при получении URL: {str(e)}", color=ERROR_COLOR)
    return None

def save_photo(data, token):
    url = "https://api.vk.com/method/photos.saveOwnerPhoto"
    params = {
        'server': data.get('server'),
        'photo': data.get('photo'),
        'hash': data.get('hash'),
        'access_token': token,
        'v': '5.131'
    }
    try:
        response = requests.post(url, params=params, timeout=10)
        if response.status_code == 200:
            return response.json().get('response', {})
    except Exception as e:
        log(f"Исключение при сохранении фото: {str(e)}", color=ERROR_COLOR)
    return {}

def delete_last_wall_post(token):
    url = "https://api.vk.com/method/wall.get"
    params = {'access_token': token, 'v': '5.131', 'count': 1}
    try:
        response = requests.get(url, params=params, timeout=10)
        data = response.json()
        if response.status_code == 200 and 'response' in data:
            items = data['response'].get('items', [])
            if items:
                last_post_id = items[0]['id']
                log(f"Последняя запись найдена: ID {last_post_id}", color=INFO_COLOR)
                delete_url = "https://api.vk.com/method/wall.delete"
                delete_params = {'access_token': token, 'v': '5.131', 'post_id': last_post_id}
                delete_response = requests.post(delete_url, params=delete_params, timeout=10)
                if delete_response.status_code == 200:
                    log(f"Последняя запись успешно удалена: ID {last_post_id}", color=SUCCESS_COLOR)
                else:
                    log(f"Ошибка при удалении записи: {delete_response.text}", color=ERROR_COLOR)
            else:
                log("Нет записей для удаления.", color=INFO_COLOR)
        else:
            log(f"Ошибка получения записей со стены: {response.text}", color=ERROR_COLOR)
    except Exception as e:
        log(f"Исключение при удалении записи: {str(e)}", color=ERROR_COLOR)

def prepare_image(image_path):
    try:
        image = Image.open(image_path)
        if image.width != image.height:
            new_size = max(image.width, image.height)
            new_image = Image.new("RGBA", (new_size, new_size), (255, 255, 255, 0)) # Прозрачный фон для RGBA
            new_image.paste(image, ((new_size - image.width) // 2, (new_size - image.height) // 2))
        else:
            new_image = image.convert("RGBA") # Убедимся что RGBA для прозрачности если есть

        new_image_with_border = ImageOps.expand(new_image, border=border_size, fill=border_color)
        
        # Сохраняем с поддержкой прозрачности, если она есть
        prepared_image_path = os.path.join(avatars_path, "prepared_image.png")
        new_image_with_border.save(prepared_image_path, format="PNG")
        return prepared_image_path
    except Exception as e:
        log(f"Ошибка подготовки изображения: {str(e)}", color=ERROR_COLOR)
        return None

def upload_avatar():
    global token_counter
    processed_count = 0
    success_count = 0
    fail_count = 0

    while tokens and avatars:
        pause_event.wait()

        token_counter += 1
        token = tokens.pop(0)
        avatar_file = avatars.pop(0)

        processed_count += 1
        processed_photos.set(f"Обработано фотографий: {processed_count}")
        remaining_photos.set(f"Осталось фотографий: {len(avatars)}")

        user_info = get_user_info(token)
        user_name = f"{user_info.get('first_name', '')} {user_info.get('last_name', '')}" if user_info else "Неизвестный пользователь"

        log(f"{token_counter}. Токен (пользователь: {user_name}): {token}", color=INFO_COLOR)
        log(f"Обработка файла: {avatar_file}", color=INFO_COLOR)

        upload_url = get_upload_url(token)
        if not upload_url:
            log(f"Не удалось получить URL для загрузки для {user_name}.", color=ERROR_COLOR)
            fail_count += 1; failed_uploads.set(f"Неуспешных добавлений: {fail_count}")
            log_fail_token(token); log_failed_account(token)
            try: os.remove(avatar_file)
            except Exception as e: log(f"Ошибка удаления файла: {str(e)}", color=ERROR_COLOR)
            delay_and_continue(); continue

        upload_success = False
        response_from_server = None
        for attempt in range(1, 4):
            prepared_image_path = prepare_image(avatar_file)
            if not prepared_image_path:
                log(f"Попытка {attempt}: Не удалось подготовить изображение для {user_name}.", color=ERROR_COLOR)
                continue
            try:
                with open(prepared_image_path, 'rb') as file_to_upload:
                    files = {'photo': file_to_upload}
                    response_from_server = requests.post(upload_url, files=files, timeout=20)
                if response_from_server.status_code != 200:
                    log(f"Попытка {attempt}: Ошибка загрузки фото для {user_name}: {response_from_server.text}", color=ERROR_COLOR)
                else:
                    upload_success = True; break
            except Exception as e:
                log(f"Попытка {attempt}: Исключение при загрузке: {str(e)}", color=ERROR_COLOR)
            finally:
                if os.path.exists(prepared_image_path):
                    try: os.remove(prepared_image_path)
                    except Exception as e: log(f"Ошибка удаления подготовленного изображения: {str(e)}", color=ERROR_COLOR)
            if attempt < 3 and not upload_success:
                log(f"Повторная попытка загрузки фото для {user_name}...", color=INFO_COLOR)
                time.sleep(1)

        if not upload_success:
            log(f"Не удалось загрузить фото для {user_name} после 3 попыток.", color=ERROR_COLOR)
            fail_count += 1; failed_uploads.set(f"Неуспешных добавлений: {fail_count}")
            log_fail_token(token); log_failed_account(token)
            try: os.remove(avatar_file)
            except Exception as e: log(f"Ошибка удаления неудачной фотографии: {str(e)}", color=ERROR_COLOR)
            delay_and_continue(); continue

        log(f"Фото успешно загружено на сервер для {user_name}.", color=SUCCESS_COLOR)
        try: upload_data = response_from_server.json()
        except Exception as e: log(f"Ошибка преобразования ответа в JSON: {str(e)}", color=ERROR_COLOR); upload_data = {}
        
        save_response = save_photo(upload_data, token)
        if save_response.get("saved") == 1:
            log(f"Фото сохранено для {user_name}. ID поста: {save_response.get('post_id')}", color=SUCCESS_COLOR)
            delete_last_wall_post(token)
            success_count += 1; successful_uploads.set(f"Успешных добавлений: {success_count}")
            log_success_token(token)
        else:
            log(f"Ошибка сохранения фото для {user_name}. Ответ: {save_response}", color=ERROR_COLOR)
            fail_count += 1; failed_uploads.set(f"Неуспешных добавлений: {fail_count}")
            log_fail_token(token); log_failed_account(token)
        try:
            destination_path = os.path.join(uploaded_path, os.path.basename(avatar_file))
            shutil.move(avatar_file, destination_path)
        except Exception as e: log(f"Ошибка перемещения оригинального файла: {str(e)}", color=ERROR_COLOR)
        log("—" * 50, color=SEPARATOR_COLOR)
        delay_and_continue()

    log("Загрузка аватарок завершена. Все операции выполнены.", color=SUCCESS_COLOR)
    if upload_button: upload_button["state"] = "normal"
    if pause_button: pause_button["state"] = "disabled"
    if resume_button: resume_button["state"] = "disabled"


def delay_and_continue():
    try:
        min_delay_val = float(delay_min.get())
        max_delay_val = float(delay_max.get())
        if max_delay_val < min_delay_val: max_delay_val = min_delay_val # Гарантируем правильный диапазон
        delay = random.uniform(min_delay_val, max_delay_val)
        log(f"Задержка перед следующим аккаунтом: {delay:.2f} секунд", color=INFO_COLOR)
        time.sleep(delay) # Используем time.sleep, т.к. pause_event здесь не нужен - это меж-аккаунтная задержка
    except ValueError:
        log("Ошибка в значениях задержки. Установлена задержка: 2.5 секунды.", color=ERROR_COLOR)
        time.sleep(2.5)


def start_upload_thread():
    log("Нажата кнопка 'Запустить'", color=INFO_COLOR)
    upload_button["state"] = "disabled"
    pause_button["state"] = "normal"
    resume_button["state"] = "disabled"
    pause_event.set() # Убедимся, что процесс не на паузе при старте
    threading.Thread(target=upload_avatar, daemon=True).start()

def pause_process():
    log("Нажата кнопка 'Приостановить'", color=INFO_COLOR)
    pause_event.clear()
    pause_button["state"] = "disabled"
    resume_button["state"] = "normal"

def resume_process():
    log("Нажата кнопка 'Восстановить'", color=INFO_COLOR)
    pause_event.set()
    resume_button["state"] = "disabled"
    pause_button["state"] = "normal"

def clear_all_logs():
    log_text.delete(1.0, tk.END)
    success_text.delete(1.0, tk.END)
    fail_text.delete(1.0, tk.END)

last_clicked_text_widget = None
def create_context_menu(event):
    global last_clicked_text_widget
    last_clicked_text_widget = event.widget
    context_menu.tk_popup(event.x_root, event.y_root)

def copy_selected_text():
    global last_clicked_text_widget
    if not last_clicked_text_widget: return
    try:
        selected_text = last_clicked_text_widget.selection_get()
        root.clipboard_clear()
        root.clipboard_append(selected_text)
    except tk.TclError: pass

# ----------------------------------------------------------------------------
# Построение интерфейса
# ----------------------------------------------------------------------------
main_frame = ttk.Frame(root, padding=10)
main_frame.pack(fill=tk.BOTH, expand=True)

top_frame = ttk.Frame(main_frame)
top_frame.pack(fill=tk.X, pady=5)

upload_button = ttk.Button(top_frame, text="Запустить", command=start_upload_thread, width=15)
upload_button.pack(side=tk.LEFT, padx=5)
pause_button = ttk.Button(top_frame, text="Приостановить", command=pause_process, width=15, state="disabled")
pause_button.pack(side=tk.LEFT, padx=5)
resume_button = ttk.Button(top_frame, text="Восстановить", command=resume_process, width=15, state="disabled")
resume_button.pack(side=tk.LEFT, padx=5)
update_button = ttk.Button(top_frame, text="Обновить информацию", command=update_info, width=20)
update_button.pack(side=tk.LEFT, padx=5)

delay_label = ttk.Label(top_frame, text="Задержка (сек):")
delay_label.pack(side=tk.LEFT, padx=(10, 5))
delay_min_entry = ttk.Entry(top_frame, textvariable=delay_min, width=5)
delay_min_entry.pack(side=tk.LEFT)
delay_dash_label = ttk.Label(top_frame, text="-")
delay_dash_label.pack(side=tk.LEFT)
delay_max_entry = ttk.Entry(top_frame, textvariable=delay_max, width=5)
delay_max_entry.pack(side=tk.LEFT)

clear_log_button = ttk.Button(top_frame, text="Очистить все журналы", command=clear_all_logs, width=18)
clear_log_button.pack(side=tk.LEFT, padx=5)

stats_frame = ttk.Frame(main_frame)
stats_frame.pack(fill=tk.X, pady=5)
ttk.Label(stats_frame, textvariable=total_accounts).pack(anchor=tk.W)
ttk.Label(stats_frame, textvariable=processed_photos).pack(anchor=tk.W)
ttk.Label(stats_frame, textvariable=remaining_photos).pack(anchor=tk.W)
ttk.Label(stats_frame, textvariable=successful_uploads).pack(anchor=tk.W)
ttk.Label(stats_frame, textvariable=failed_uploads).pack(anchor=tk.W)

# Глобальные ссылки на метки для обновления их цвета
wifi_label_ui_ref = ttk.Label(stats_frame, textvariable=current_wifi_status)
wifi_label_ui_ref.pack(anchor=tk.W, pady=(5,0))

ip_country_label_ui_ref = ttk.Label(stats_frame, textvariable=current_ip_country_status)
ip_country_label_ui_ref.pack(anchor=tk.W, pady=(2,0)) # Небольшой отступ


bottom_frame = ttk.Frame(main_frame)
bottom_frame.pack(fill=tk.BOTH, expand=True, pady=(5, 0))
bottom_frame.columnconfigure(0, weight=1); bottom_frame.columnconfigure(1, weight=1); bottom_frame.columnconfigure(2, weight=1)

log_frame = ttk.Frame(bottom_frame, relief=tk.SUNKEN)
log_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
ttk.Label(log_frame, text="Общий журнал").pack(anchor="n")
log_text = tk.Text(log_frame, height=15, wrap=tk.WORD, bg="#34495E", fg="white", font=default_font)
log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
log_scroll = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=log_text.yview); log_scroll.pack(side=tk.RIGHT, fill=tk.Y)
log_text.config(yscrollcommand=log_scroll.set)

success_frame = ttk.Frame(bottom_frame, relief=tk.SUNKEN)
success_frame.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
ttk.Label(success_frame, text="Успешные токены").pack(anchor="n")
success_text = tk.Text(success_frame, height=15, wrap=tk.WORD, bg="#34495E", fg="white", font=default_font)
success_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
success_scroll = ttk.Scrollbar(success_frame, orient=tk.VERTICAL, command=success_text.yview); success_scroll.pack(side=tk.RIGHT, fill=tk.Y)
success_text.config(yscrollcommand=success_scroll.set)

fail_frame = ttk.Frame(bottom_frame, relief=tk.SUNKEN)
fail_frame.grid(row=0, column=2, sticky="nsew", padx=5, pady=5)
ttk.Label(fail_frame, text="Неуспешные токены").pack(anchor="n")
fail_text = tk.Text(fail_frame, height=15, wrap=tk.WORD, bg="#34495E", fg="white", font=default_font)
fail_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
fail_scroll = ttk.Scrollbar(fail_frame, orient=tk.VERTICAL, command=fail_text.yview); fail_scroll.pack(side=tk.RIGHT, fill=tk.Y)
fail_text.config(yscrollcommand=fail_scroll.set)

context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="Копировать", command=copy_selected_text)
context_menu.add_separator()
context_menu.add_checkbutton(label="Автопрокрутка", variable=auto_scroll_enabled)

log_text.bind("<Button-3>", create_context_menu)
success_text.bind("<Button-3>", create_context_menu)
fail_text.bind("<Button-3>", create_context_menu)

# Запуск обработки очередей и первоначальное обновление информации
process_log_queue()
process_success_queue()
process_fail_queue()
update_info()

# Запуск периодического обновления Wi-Fi и IP/Страны
update_wifi_display()
process_ip_country_queue() # Начинаем обрабатывать очередь IP/страны
update_ip_country_display() # Первый вызов для обновления IP и страны

root.mainloop()